﻿// <copyright file="AppNotificationSwitchableClient.cs" company="Robert Venables">
// Copyright (c) 2009 Robert Venables.
// </copyright>
// <author>Robert Venables</author>
// <email>rob@rvenables.com</email>

#define CODE_ANALYSIS

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;

namespace VNotify
{
    /// <summary>
    /// Client that works with any type of IAppNoficationSender.
    /// </summary>
    public class AppNotificationSwitchableClient
    {
        /// <summary>
        /// The factory used to create the AppNotificationSender objects.
        /// Abstracted to make testing easier.
        /// </summary>
        private IAppNotificationSenderFactory senderFactory = null;

        /// <summary>
        /// Initializes a new instance of the AppNotificationSwitchableClient class
        /// with a specific AppNotificationSenderFactory.
        /// The factory is used to create all of the sender objects
        /// throughout the lifetime of the client.
        /// </summary>
        /// <param name="senderFactory">
        /// The specific factory used to create the
        /// sender objects used in the client.
        /// </param>
        public AppNotificationSwitchableClient(IAppNotificationSenderFactory senderFactory)
        {
            if (senderFactory == null)
                throw new ArgumentNullException("senderFactory");

            this.senderFactory = senderFactory;
        }

        #region "Asynchronous Notification Methods (And Support)"

            /// <summary>
            /// Sends an AppNotification Asynchronously (APM).
            /// Paired with EndSend(...)
            /// </summary>
            /// <param name="notification">The individual notification to send.</param>
            /// <param name="recipient">The recipient to receive the notification.</param>
            /// <param name="callback">Optional callback which will be executed when the send is complete.</param>
            /// <returns>AppNotificationAsyncResult containing the relevant state.</returns>
            [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
                Justification = "This APM implementation correctly rethrows the exception later during EndSend(...).")]    
            public IAsyncResult BeginSend(
                                    AppNotification notification,
                                    AppNotificationRecipient recipient,
                                    AsyncCallback callback)
            {
                if (notification == null)
                    throw new ArgumentNullException("notification");

                if (recipient == null)
                    throw new ArgumentNullException("recipient");

                AppNotificationAsyncResult workStateTracker
                    = new AppNotificationAsyncResult(callback, notification);

                Thread workThread = new Thread(() =>
                {
                    Exception workException = null;

                    try
                    {
                        Send(notification, recipient);
                    }
                    catch (Exception CoughtException)
                    {
                        workException = CoughtException;
                    }

                    workStateTracker.RegisterCompleted(workException, false);
                })
                {
                    Name = "BeginSend[A] Worker"
                };

                workThread.Start();

                return workStateTracker;
            }

            /// <summary>
            /// Sends an AppNotification to a list of Recipients Asynchronously (APM).
            /// Paired with EndSend(...)
            /// </summary>
            /// <param name="notification">The individual notification to send.</param>
            /// <param name="recipients">The list of recipients to receive the notification.</param>
            /// <param name="callback">Optional callback which will be executed when the send is complete.</param>
            /// <returns>AppNotificationAsyncResult containing the relevant state.</returns>
            [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
                Justification = "This APM implementation correctly rethrows the exception later during EndSend(...).")]    
            public IAsyncResult BeginSend(
                                        AppNotification notification,
                                        IEnumerable<AppNotificationRecipient> recipients,
                                        AsyncCallback callback)
            {
                if (notification == null)
                    throw new ArgumentNullException("notification");

                if (recipients == null || recipients.Count() == 0)
                    throw new ArgumentException("Invalid recipients list");

                AppNotificationAsyncResult workStateTracker
                    = new AppNotificationAsyncResult(callback, notification);

                Thread workThread = new Thread(() =>
                {
                    Exception workException = null;

                    try
                    {
                        Send(notification, recipients);
                    }
                    catch (Exception CoughtException)
                    {
                        workException = CoughtException;
                    }

                    workStateTracker.RegisterCompleted(workException, false);
                })
                {
                    Name = "BeginSend[B] Worker"
                };

                workThread.Start();

                return workStateTracker;
            }

            /// <summary>
            /// End method of BeginSend(...)
            /// </summary>
            /// <param name="asyncResult">Required IAsyncResult returned by BeginSend</param>
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic",
                Justification = "EndXxx is traditionally non-static in the APM and it may be confusing to some library users to encounter a static EndSend method.")]    
            public void EndSend(IAsyncResult asyncResult)
            {
                // Wait for send to finish
                ((AppNotificationAsyncResult)asyncResult).EndInvoke();
            }

        #endregion

        #region "Synchronous Notification Methods"

            /// <summary>
            /// Sends a single Notification to the 
            /// single target Recipient.
            /// </summary>
            /// <param name="notification">The individual notification to send.</param>
            /// <param name="recipient">The recipient to receive the notification.</param>
            public void Send(AppNotification notification, AppNotificationRecipient recipient)
            {
                if (notification == null)
                    throw new ArgumentNullException("notification");

                if (recipient == null)
                    throw new ArgumentNullException("recipient");

                IAppNotificationSender sender
                    = this.senderFactory.BuildSender(recipient.ApiKey);

                sender.SendNotification(notification);
            }

            /// <summary>
            /// Sends a single Notification to ALL of the
            /// target Recipients.
            /// </summary>
            /// <param name="notification">The individual notification to send.</param>
            /// <param name="recipients">The list of recipients to send the notification to.</param>
            public void Send(
                        AppNotification notification,
                        IEnumerable<AppNotificationRecipient> recipients)
            {
                if (notification == null)
                    throw new ArgumentNullException("notification");

                if (recipients == null || recipients.Count() == 0)
                    throw new ArgumentException("No recipients to send to.");

                /* Note: Recipients list is mutable and could be modified
                 * in another thread during the foreach loop, causing an
                 * InvalidOperationException. The quick shallow copy below
                 * reduces the chances of this happening.
                 */

                List<AppNotificationRecipient> workingRecipients
                    = new List<AppNotificationRecipient>();

                recipients.ToList().ForEach(r => workingRecipients.Add(r));

                foreach (AppNotificationRecipient recipient in workingRecipients)
                {
                    IAppNotificationSender sender
                        = this.senderFactory.BuildSender(recipient.ApiKey);

                    sender.SendNotification(notification);
                }
            }

            /// <summary>
            /// Sends the target Message to the specified Recipient.
            /// </summary>
            /// <param name="message">The message that will appear in the notification popup.</param>
            /// <param name="recipient">The target recipient to receive the message.</param>
            public void Send(string message, AppNotificationRecipient recipient)
            {
                if (string.IsNullOrEmpty(message))
                    throw new ArgumentException("Message is Invalid.");

                if (recipient == null)
                    throw new ArgumentNullException("recipient");

                AppNotification notification = new AppNotification(message);

                IAppNotificationSender sender
                    = this.senderFactory.BuildSender(recipient.ApiKey);

                sender.SendNotification(notification);
            }

            /// <summary>
            /// Sends the target Message to the specified APIKey recipient.
            /// </summary>
            /// <param name="message">The message that will appear in the notification popup.</param>
            /// <param name="apiKey">The target recipient's API Key.</param>
            public void Send(string message, string apiKey)
            {
                if (string.IsNullOrEmpty(message))
                    throw new ArgumentException("Message is Invalid.");

                if (string.IsNullOrEmpty(apiKey))
                    throw new ArgumentException("APIKey is Invalid.");

                AppNotification notification = new AppNotification(message);

                IAppNotificationSender sender
                    = this.senderFactory.BuildSender(apiKey);

                sender.SendNotification(notification);
            }

        #endregion
    }
}